//*****************************************************************************
//

#include "hw_ints.h"
#include "hw_memmap.h"
#include "hw_types.h"
#include "debug.h"
#include "interrupt.h"
#include "sysctl.h"
#include "timer.h"
#include "osram96x16x1.h"
#include "gpio.h"
#include "adc.h"
#include "uart.h"
#include "bitmaps.h"
#include <stdlib.h>

#define POINT_X 48
#define IMAGE_WIDTH 45
#define LAB_MA3X_SIZE 315
#define SCREEN_WIDTH 96

#define PUSH_BUTTON GPIO_PIN_4

//*****************************************************************************
//Global variables:
//*****************************************************************************
unsigned char buffer[IMAGE_WIDTH * 2] = {0};
unsigned char buff_zero[1] = {0x00};
//*****************************************************************************
short next_Y = 0;
static short disp_Y = 0;			// offset in pixels on display (0 - 40) 
static short lab_Y = 0;				// offset in lab
unsigned long offset_X = 48; 		// keeps calculated and filtrated value of ADC, and passes it to the "draw" function
short flag = 0; 					// keeps the current button value
const unsigned short epsilon = 30;	// used for filtration of the ADC
unsigned short state;				//used for click doubleclick determination
unsigned long count;
unsigned short destination = 0;
unsigned char is_there_wall[4];	// 0 - can't move, 1- can move; index ( can_move): 0 for X , 1 for -X , 2 for Y, 3 for -Y destinations

//*****************************************************************************
//function prototypes:

void DisplayPoint(void);
void ClearBackground(void);
void delay(unsigned long);
void can_move(unsigned char*);
void move(unsigned long);
void move_vertical(void);
void Draw(void);

//
//*****************************************************************************
//

#ifdef DEBUG
void
__error__(char *pcFilename, unsigned long ulLine)
{
}
#endif

//*****************************************************************************

int
main(void)
{
	unsigned long old_val; //keeps the previous value of the ADC
	unsigned long tmp_val = 0; //keeps the current value of the ADC	
	//
    // Set the clocking to run at 20MHz from the PLL.
    //
    SysCtlClockSet(SYSCTL_SYSDIV_10 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN |
                   SYSCTL_XTAL_6MHZ);
 	
    //
    // Initialize the OLED display and write status.
    //
	OSRAM96x16x1Init(true);
	//
    // Enable the peripherals used by this example.
    //
	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);
	SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC);
	SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
	SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER1);
	SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER2);
	
	//Enable all Interrupts
	IntMasterEnable();
	
	//configure the Button
	IntEnable(INT_GPIOC);    	
 	GPIOPinTypeGPIOInput(GPIO_PORTC_BASE, PUSH_BUTTON);
    GPIOIntTypeSet(GPIO_PORTC_BASE,PUSH_BUTTON,GPIO_RISING_EDGE);
	GPIOPinIntEnable(GPIO_PORTC_BASE, PUSH_BUTTON);

	//
    // Configure the two 32-bit one shot timers.
    //
    TimerConfigure(TIMER0_BASE, TIMER_CFG_32_BIT_OS);
    TimerConfigure(TIMER1_BASE, TIMER_CFG_32_BIT_OS);
	TimerConfigure(TIMER2_BASE, TIMER_CFG_32_BIT_OS);
	TimerLoadSet(TIMER0_BASE, TIMER_A, SysCtlClockGet()/4);
   	TimerLoadSet(TIMER1_BASE, TIMER_A, SysCtlClockGet()/6);
	TimerLoadSet(TIMER2_BASE, TIMER_A, SysCtlClockGet()/6);

    //
    // Configure the ADC
    //
    ADCSequenceConfigure(ADC_BASE, 0, ADC_TRIGGER_PROCESSOR, 0);
    ADCSequenceStepConfigure(ADC_BASE, 0, 0, ADC_CTL_CH0 | ADC_CTL_IE | ADC_CTL_END);
    ADCSequenceEnable(ADC_BASE, 0);

	ADCIntEnable(ADC_BASE, 0);
	
	//
    // Setup the interrupts for the timer timeouts.
    //
    IntEnable(INT_TIMER0A);
    TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
    IntEnable(INT_TIMER1A);
    TimerIntEnable(TIMER1_BASE, TIMER_TIMA_TIMEOUT);
	IntEnable(INT_TIMER2A);
    TimerIntEnable(TIMER2_BASE, TIMER_TIMA_TIMEOUT);
	

	for(count = 0; count < 2*IMAGE_WIDTH; count++)	// fill the buffer
		buffer[count] = lab_base[count];
	
	//Display Logo
	OSRAMImageDraw(logo, 0, 0, 96, 2);
	delay(15000000);

	//
    // Loop forever
    //	
	for(;;) {		
		
		ADCProcessorTrigger(ADC_BASE, 0);
		
		while(!ADCIntStatus(ADC_BASE, 0, false)) {}
	
		ADCSequenceDataGet(ADC_BASE, 0, &tmp_val);
		
		if(tmp_val > epsilon) {
			
			if(old_val > tmp_val + epsilon)
				destination = 0;

			if(old_val < tmp_val - epsilon)
				destination = 1;
						
			old_val = tmp_val;	
		}

	    if(state == 1) {
			destination = 2;
			state = 0;
		}

		if(state == 2) {
			destination = 3;
			state = 0;
		}
		can_move(is_there_wall);
		
		//
		//Enable Timer2 to slow the motion.
		//Timer2 is a OS timer. After time expires generates an interrupt
		//and the interrupt handler calls the function "move(unsigned short destination)"
		//	
		TimerEnable(TIMER2_BASE, TIMER_A);
					
		if(disp_Y >= 40)			
			break;
	
		Draw();
	
	//end for	
	}
	OSRAMClear();
	for(;;) {
		//OSRAMStringDraw("Game Over", 25, 0);
		OSRAMImageDraw(thumb, 45, 0, 15, 2);
	}

//end main
}

//*****************************************************************************
void
Draw(void) {
	unsigned char tmp_buffer_val1, tmp_buffer_val2;

	tmp_buffer_val1 = buffer[POINT_X + 1 - offset_X + IMAGE_WIDTH];
	tmp_buffer_val2 = buffer[POINT_X + 2 - offset_X + IMAGE_WIDTH];		
	
	DisplayPoint();
	
	OSRAMImageDraw(buffer, offset_X, 0, IMAGE_WIDTH, 2);

	buffer[POINT_X + 1 - offset_X + IMAGE_WIDTH] = tmp_buffer_val1;
	buffer[POINT_X + 2 - offset_X + IMAGE_WIDTH] = tmp_buffer_val2;
	
	ClearBackground();	
}
//*****************************************************************************
//function, that displays the point at the middle of the OLED display
void
DisplayPoint(void) {

	buffer[POINT_X +1 - offset_X + IMAGE_WIDTH] |= 3;
	buffer[POINT_X + 2 - offset_X + IMAGE_WIDTH] |= 3;
}
//				
//****************************************************************************
//	Clears the rest of display (writes zeroes)
//****************************************************************************
//
void
ClearBackground(void) {
	
	for(count = 0; count <= SCREEN_WIDTH; count++)
	{
		if(count == offset_X)
			count +=  IMAGE_WIDTH;
		
		OSRAMImageDraw(buff_zero, count, 0, 1, 1);
		OSRAMImageDraw(buff_zero, count, 1, 1, 1);	
	}
}
//*****************************************************************************
void move_vertical(void)
{
	short i, mask;
	unsigned char tmp;

	disp_Y += next_Y;
	
	lab_Y = disp_Y / 8;
	i = disp_Y % 8;

	switch(i)
	{	
		case 0:
			for(count = 0; count < 2 * IMAGE_WIDTH ; count++)
				buffer[count] = lab_base[lab_Y * IMAGE_WIDTH + count];
			return;
		case 1:
			mask = 0x01; break;
		case 2:
			mask = 0x03; break;
		case 3:
			mask = 0x07; break;
		case 4:
			mask = 0x0F; break;
		case 5:
			mask = 0x1F; break;
		case 6:
			mask = 0x3F; break;
		case 7:
			mask = 0x7F; break;
	}

	for(count = 0; count < 2 * IMAGE_WIDTH ; count++)
	{
		buffer[count] = 0;
		buffer[count] = lab_base[lab_Y * IMAGE_WIDTH + count] >> i;
		tmp = lab_base[(lab_Y + 1 )* IMAGE_WIDTH + count] & mask;
		tmp <<= (8 - i);
		buffer[count] |= tmp;
	}

}
//*****************************************************************************
void move(unsigned long destination)
{  	 	
	switch(destination)
	{	
		case 0:
		if(disp_Y >= 40 || disp_Y < 0)
			return;
		offset_X++; break;
		
		case 1:
		if(disp_Y >= 40 || disp_Y < 0)
			return;
		offset_X--; break;
		
		case 2:
		next_Y = 1;
		move_vertical(); break;
		
		case 3:
		if(disp_Y <= 0)
			return;
		next_Y = -1;
		move_vertical(); break;
	}
}
//*****************************************************************************
void can_move(unsigned char* is_there_wall)
{
	is_there_wall[0] = buffer[IMAGE_WIDTH - offset_X + POINT_X] & 0x03;			 // right
	is_there_wall[1] = buffer[IMAGE_WIDTH - offset_X + POINT_X + 3 ] & 0x03;	 // left  
	
	is_there_wall[2] = (buffer[IMAGE_WIDTH - offset_X + POINT_X + 1] & 0x04)
						 | (buffer[IMAGE_WIDTH - offset_X + POINT_X + 2] & 0x04);	// down through labyrinth

	is_there_wall[3] = (buffer[POINT_X - offset_X + 1] & 0x80)
						 | (buffer[POINT_X - offset_X + 2] & 0x80);				// up through labyrinth
}

//*****************************************************************************

void delay(unsigned long a) {
	while(--a);
}

//*****************************************************************************
//
// The interrupt handler for Timer0 interrupt.
//
//*****************************************************************************
//
void
Timer0IntHandler(void) {
	//
    // Clear the timer interrupt.
    //
	TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
	
	state = abs(flag);

	flag = 0;		
}

//*****************************************************************************
//
// The interrupt handler for Timer1 interrupt.
//
//*****************************************************************************
void
Timer1IntHandler(void) {
    //
    // Clear the timer interrupt.
    //
   	TimerIntClear(TIMER1_BASE, TIMER_TIMA_TIMEOUT);

	if(flag == -1)	 
		flag = 1;
	else if (flag == -2)
		flag = 2;
}
//
//*****************************************************************************
//
//The interrupt handler for Timer2 interrupt.
//
//*****************************************************************************
//
void
Timer2IntHandler(void) {
	//
    // Clear the timer interrupt.
    //
	TimerIntClear(TIMER2_BASE, TIMER_TIMA_TIMEOUT);
	if(is_there_wall[destination] == 0)
		move(destination);
}
//
//*****************************************************************************
//
//The interrupt handler for interrupt, generated by the button.
//
//*****************************************************************************
//
void 
GPIOC_IntHandler(void) {
	//
    // Clear the GPIOC interrupt.
    //
	GPIOPinIntClear(GPIO_PORTC_BASE, PUSH_BUTTON);
	
	if(flag == 0) {
		TimerEnable(TIMER1_BASE, TIMER_A);
		TimerEnable(TIMER0_BASE, TIMER_A);
	   	flag = -1;
	}	
 	else if(flag == 1) {
		TimerEnable(TIMER1_BASE, TIMER_A);
		flag = -2;
	}
}

